`timescale 1ns/100ps

`include "sim_glb.sv"

module tc;

localparam          CLK_PRD                 = 6.0;
localparam          CDW                     = 15;
localparam          CFW                     = 14;
localparam          IDW                     = 12;
localparam          IFW                     = 9;
localparam          ODW                     = 12;
localparam          OFW                     = 9;
localparam          TAP_NUM                 = 15;       // must TAP_NUM>=2
localparam          COE_NUM                 = TAP_NUM/2 + TAP_NUM%2;
localparam          COE_ZERO_MAP            = 8'b0010_1010;  // for saving area
localparam          CSCD_LEN_MAX            = 4;    // CSCD_LEN_MAX>=2, cascade length, if too bigger, bad Fmax
localparam          LVL_REG                 = 3'b101;  // specify register for each level

reg                                         rst_n;
reg                                         clk;
reg                                         cke;

wire                [TAP_NUM*CDW-1:0]       in_coe;     // {CM-1, .... C1, C0}, M=COE_NUM
wire        signed  [IDW-1:0]               in_dat;
wire        signed  [ODW-1:0]               out_dat;

initial begin:CRG
    rst_n=1'b0;
    clk=1'b1;
    cke=1'b1;

    fork
        rst_n=#100.5 1'b1;
        forever clk=#CLK_PRD ~clk;
        forever begin
            repeat(3) begin
                @(posedge clk); 
            end
            cke=`U_DLY 1'b1;
            @(posedge clk);
            cke=`U_DLY 1'b0;
        end
    join
end

RGRS_MNG    rgrs;
initial begin:REGRESS
    rgrs = new("tc_sym_coe_fir", 2);

    rgrs.wait_chks_done(100_000_000);
end

initial begin:GEN_
    @(posedge rst_n);

    #100_000;
    rgrs.one_chk_done("gen num is done.");
end

reg         signed  [16-1:0]                wav_re;

initial begin:COS
    integer n;
    real pr;
    real w;
    real ph;

    wav_re = 0;
//    wav_im = 0;
    n = 0;
    ph = 0;

    @(posedge rst_n);
    forever begin
        @(posedge clk);
        pr = 128.0*$exp(-n/10000.0)+2.1;
//        pr = 512;
        w = 2*3.1415927/pr;
        ph = ph+w;
        wav_re <=`U_DLY $cos(ph)*32767.0;
//        wav_im <=`U_DLY $sin(ph)*2047;
        n = n+1;
    end
end

s_sat_tru #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (16                             ),	// input data width
        .IFW                            (13                             ),	// input fractional width,  IFW>=IDW is legal
        .ODW                            (IDW                            ),	// output data width
        .OFW                            (IFW                            )	// output fractional width, OFW>=ODW is legal
) u_tru ( 
        .id                             (wav_re                         ),	// s(IDW, IFW), the MSB is sign
        .od                             (in_dat                         ),	// s(ODW, OFW), the MSB is sign
        .over                           (                               )
);

assign in_coe[0*CDW+:CDW] = -15'sd53;
assign in_coe[1*CDW+:CDW] =  15'sd0;
assign in_coe[2*CDW+:CDW] =  15'sd313;
assign in_coe[3*CDW+:CDW] =  15'sd0;
assign in_coe[4*CDW+:CDW] = -15'sd1155;
assign in_coe[5*CDW+:CDW] =  15'sd0;
assign in_coe[6*CDW+:CDW] =  15'sd4989;
assign in_coe[7*CDW+:CDW] =  15'sd8192;
sym_coe_fir #(
        .CDW                            (CDW                            ),
        .CFW                            (CFW                            ),
        .IDW                            (IDW                            ),
        .IFW                            (IFW                            ),
        .ODW                            (ODW                            ),
        .OFW                            (OFW                            ),
        .TAP_NUM                        (TAP_NUM                        ),	// must TAP_NUM>=2
        .COE_ZERO_MAP                   (COE_ZERO_MAP                   ),	// for saving area
        .CSCD_LEN_MAX                   (CSCD_LEN_MAX                   ),	// CSCD_LEN_MAX>=2, cascade length, if too bigger, bad Fmax
        .LVL_REG                        (LVL_REG                        )	// specify register for each level
) u_dut ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

        .in_vld                         (cke                            ),
        .in_coe                         (in_coe[0+:8*CDW]               ),	// {CM-1, .... C1, C0}, M
        .in_dat                         (in_dat                         ),

        .out_vld                        (                               ),
        .out_dat                        (out_dat                        )
);

wire        signed  [ODW-1:0]               out_dat_pp;

assign in_coe[14*CDW+:CDW] = -15'sd53;
assign in_coe[13*CDW+:CDW] =  15'sd0;
assign in_coe[12*CDW+:CDW] =  15'sd313;
assign in_coe[11*CDW+:CDW] =  15'sd0;
assign in_coe[10*CDW+:CDW] = -15'sd1155;
assign in_coe[ 9*CDW+:CDW] =  15'sd0;
assign in_coe[ 8*CDW+:CDW] =  15'sd4989;
fir_pp #(
        .CDW                            (CDW                            ),
        .CFW                            (CFW                            ),
        .IDW                            (IDW                            ),
        .IFW                            (IFW                            ),
        .ODW                            (ODW                            ),
        .OFW                            (OFW                            ),
        .TAP_NUM                        (TAP_NUM                        ),	// must TAP_NUM>=2
        .COPY_NUM                       (3                              ),	// COPY_NUM>=1, the number of in_dat copy for fan-out
        .VLD_USE                        (1'b1                           ),
        .IN_REG_EN                      (1'b1                           ),
        .P_REG_EN                       (1'b1                           )
) u_fir_pp ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

        .in_vld                         (cke                            ),
        .in_coe                         (in_coe                         ),	// {CN-1, .... C1, C0}
        .in_dat                         (in_dat                         ),

        .out_vld                        (                               ),
        .out_dat                        (out_dat_pp                     )
);

reg                                         in_vld;
reg                 [TAP_NUM*IDW-1:0]       in_dat_all;
wire        signed  [ODW-1:0]               out_dat_sum;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        in_dat_all <=`U_DLY 0;
    end else if (cke==1'b1) begin
        in_dat_all <=`U_DLY (in_dat_all<<IDW) | $unsigned(in_dat);
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        in_vld <=`U_DLY 1'b0;
    end else begin
        in_vld <=`U_DLY cke;
    end
end

sum_of_prdct #(     // sum of all products
        .CDW                            (CDW                            ),	// coefficient data width
        .CFW                            (CFW                            ),	// coefficient fractional width
        .IDW                            (IDW                            ),	// input data width
        .IFW                            (IFW                            ),	// input fractional width
        .ODW                            (ODW                            ),	// output data width
        .OFW                            (OFW                            ),	// output fractional width
        .COE_NUM                        (TAP_NUM                        ),	// COE_NUM>=1
        .CSCD_LEN_MAX                   (4                              ),	// CSCD_LEN_MAX>=2, cascade length, if too bigger, bad Fmax
        .LVL_REG                        (4'b1010                        )	// specify register for each level
) u_sum_of_prdct ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

        .in_vld                         (in_vld                         ),
        .in_coe                         (in_coe                         ),	// coefficients, {CN-1, ......, c1, c0}, c0 is far away from center
        .in_dat                         (in_dat_all                     ),	// datas {x(n-N+1), ......  x(n-1), x(n)}, DLY of x(n-1) > x(n)

        .out_vld                        (                               ),
        .out_dat                        (out_dat_sum                    ),	// y(n)
        .over                           (                               )
);

reg                                         err;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        err <=`U_DLY 1'b0;
    end else if (cke==1'b1) begin
        if (out_dat_pp!=out_dat || out_dat!=out_dat_sum) begin
            err <=`U_DLY 1'b1;
            $error("[ERROR] fir out_dat is error.");
        end else begin
            err <=`U_DLY 1'b0;
        end
    end
end

initial begin:CHK_

    @(posedge rst_n);
    #500;

    #100_000;

    rgrs.one_chk_done("chk num is done.");
end

endmodule

